// Behavior originally contributed by HighVoltz.
//
// LICENSE:
// This work is licensed under the
//     Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// also known as CC-BY-NC-SA.  To view a copy of this license, visit
//      http://creativecommons.org/licenses/by-nc-sa/3.0/
// or send a letter to
//      Creative Commons // 171 Second Street, Suite 300 // San Francisco, California, 94105, USA.

#region Summary and Documentation

/*
	 RunCode has the following characteristics:
	  * It can run C# statements and coroutines.
	  * It can define variables, functions, classes or any other types

	BEHAVIOR LIMITATIONS:
		Any type, variable and function definition cannot easily be accessed from a <If/While Condition />
		because they're placed in different namespaces and classes and the code generated by RunCode is
		placed inside long namespace and in a somewhat obscure class name

	BEHAVIOR ATTRIBUTES:
		Type [optional; Default: Statement]
			This argument specifies whether the code is a statement or a variable/type/function definition

		Code [optional]
			This is the CSharp code. This attribute is optional because code can also be placed inside a CDATA node
			Whats nice about using a CDATA node is you don't need to use xml escapes for <, >, ', " and &

	BEHAVIOR ELEMENTS:
		CDATA [optional]
			Code placed inside this element does not need to be escaped.
			The Code attribute must be left out for this element to be used.
			See the examples.

	THINGS TO KNOW
		* All code is placed inside a class that inherits the ProfileHelperFunctionsBase class.
			For a list of all helpers functions and properties available check the documentation page for
			ProfileHelperFunctionsBase here http://docs.honorbuddy.com/html/45e43d39-07f7-5ddf-31ef-213694255be0.htm
		* Statements (Type="Statement") are placed inside the body of an async Task coroutine.
		* Definitions (Type="Definition") are placed inside the body of a class.
		* All definitions can be accessed from anywhere in the profile that a C# expression is used
		* Initialization value (if there is one) for variable definitions are assigned at the time the profile is
			loaded and on same thread that ProfileManager.Load is called on no matter where the RunCode is in profile.
			If you need a fresh value then delay assignment to the variable until its value needs to be updated and use
			a normal statement (Type="Statement") to update its value.

 */

#endregion

#region Examples

/*
	This is an example of how this behavior can be used to stop the bot, very basic.
	You can leave out the semicolon at the end on single line statements if you want.

	<CustomBehavior File="RunCode" Code="TreeRoot.Stop()">

	The following is an example that shows how to define a function, not very useful.

        <CustomBehavior File="RunCode" Type="Definition"><![CDATA[
                void Log(string format, params object[] args)
                {
                    Logging.Write(Colors.Green, Path.GetFileNameWithoutExtension(ProfileManager.XmlLocation)+": " + format, args);
                }
            ]]></CustomBehavior>

	The following shows how define a WaitTimer variable, reset it and loop until it expires.

 		<CustomBehavior File="RunCode" Code="WaitTimer myTimer = new WaitTimer(TimeSpan.FromSeconds(10));" Type="Definition"/>
		<CustomBehavior File="RunCode" Code="myTimer.Reset()"/>
		<While Condition="!myTimer.IsFinished">
			<!-- Do something useful here -->
		</While>

 */

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using CommonBehaviors.Actions;
using Honorbuddy.QuestBehaviorCore;
using Styx.Common;
using Styx.CommonBot.Profiles;
using Styx.CommonBot.Profiles.Quest.Order;
using Styx.Pathing;
using Styx.TreeSharp;

#endregion

namespace Honorbuddy.Quest_Behaviors
{
    [CustomBehaviorFileName(@"RunCode")]
    public class RunCode : CustomForcedBehavior
    {
        // static field members

        #region Consructor and Argument Processing

        public RunCode(Dictionary<string, string> args)
            : base(args)
        {
            try
            {
                // Parameters dealing with 'starting' the behavior...
                var code = GetAttributeAs<string>("Code", false, null, null);
                if (code == null)
                {
                    var cData = Element.DescendantNodes().OfType<XCData>().FirstOrDefault();
                    if (cData != null)
                        code = cData.Value;
                }

                Type = GetAttributeAsNullable<CodeType>("Type", false, null, null) ?? CodeType.Statement;

                if (!string.IsNullOrEmpty(code))
                {
                    if (Type == CodeType.Definition)
                    {
                        Code = code;
                    }
                    else
                    {
                        code = code.Trim();
                        if (code.Last() != ';')
                            code += ";";
                        CoroutineProducer = new DelayCompiledExpression<Func<Task>>("async () =>{" + code + "}");
                    }
                }


                QuestId = GetAttributeAsNullable<int>("QuestId", false, ConstrainAs.QuestId(this), null) ?? 0;
                QuestRequirementComplete =
                    GetAttributeAsNullable<QuestCompleteRequirement>("QuestCompleteRequirement", false, null, null) ??
                    QuestCompleteRequirement.NotComplete;
                QuestRequirementInLog =
                    GetAttributeAsNullable<QuestInLogRequirement>("QuestInLogRequirement", false, null, null) ??
                    QuestInLogRequirement.InLog;
            }

            catch (Exception except)
            {
                // Maintenance problems occur for a number of reasons.  The primary two are...
                // * Changes were made to the behavior, and boundary conditions weren't properly tested.
                // * The Honorbuddy core was changed, and the behavior wasn't adjusted for the new changes.
                // In any case, we pinpoint the source of the problem area here, and hopefully it can be quickly
                // resolved.
                LogMessage(
                    "error",
                    "BEHAVIOR MAINTENANCE PROBLEM: " + except.Message
                    + "\nFROM HERE:\n"
                    + except.StackTrace + "\n");
                IsAttributeProblem = true;
            }
        }

        // DON'T EDIT THIS--it is auto-populated by Git
        public override string VersionId => QuestBehaviorBase.GitIdToVersionId("$Id: a784b358101c8b38bdb0b158f5b237bb1ee86677 $");
        // Variables for Attributes provided by caller
        [CompileString]
        public string Code { get; private set; }

        [CompileExpression]
        public DelayCompiledExpression<Func<Task>> CoroutineProducer { get; private set; }

        private CodeType Type { get; set; }
        private int QuestId { get; set; }
        public QuestCompleteRequirement QuestRequirementComplete { get; private set; }
        public QuestInLogRequirement QuestRequirementInLog { get; private set; }

        #endregion

        #region Private and Convenience variables

        private bool _isBehaviorDone;


        #endregion

        #region Overrides of CustomForcedBehavior

        public override void OnStart()
        {
            if (Type == CodeType.Definition)
            {
                _isBehaviorDone = true;
            }
        }

        public override bool IsDone
        {
            get
            {
                return (_isBehaviorDone // normal completion
                        || !UtilIsProgressRequirementsMet(QuestId, QuestRequirementInLog, QuestRequirementComplete));
            }
        }


        #endregion

        #region Behavior

        protected override Composite CreateBehavior()
        {
            return new ActionRunCoroutine(ctx => MainCoroutine());
        }

        private async Task<bool> MainCoroutine()
        {
            if (IsDone || Type == CodeType.Definition)
                return false;

            await CoroutineProducer.CallableExpression();
            _isBehaviorDone = true;
            return true;
        }


        #endregion

        #region Embedded Types

        public enum CodeType
        {
            // code is placed in a function that returns an async Task.
            Statement,
            // code is placed inside the body of a class.
            Definition,
        }

        #endregion
    }
}

